/*
 * Copyright (c) 2009, Servoy-stuff
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 * http://www.fsf.org/licensing/licenses/lgpl.txt
 */
package net.stuff.servoy.beans.svg;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.swing.JPanel;

import org.apache.batik.bridge.CSSUtilities;
import org.apache.batik.dom.svg.SVGOMDocument;
//import org.apache.batik.script.Window;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderAdapter;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherAdapter;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent;
import org.apache.batik.util.CSSConstants;
import org.apache.batik.util.SVGConstants;
import org.mozilla.javascript.Scriptable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MouseEvent;
import org.w3c.dom.svg.SVGAnimatedPoints;
import org.w3c.dom.svg.SVGCircleElement;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGEllipseElement;
import org.w3c.dom.svg.SVGPoint;
import org.w3c.dom.svg.SVGPointList;

import com.servoy.j2db.IForm;
import com.servoy.j2db.IFormManager;
import com.servoy.j2db.dataprocessing.IFoundSet;
import com.servoy.j2db.dataprocessing.IRecord;
import com.servoy.j2db.dataui.IServoyAwareBean;
import com.servoy.j2db.plugins.IClientPluginAccess;
import com.servoy.j2db.scripting.IScriptObject;
import com.servoy.j2db.ui.IComponent;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.PersistHelper;
import com.servoy.j2db.util.Utils;

/**
 * @author Servoy Stuff
 * http://www.servoy-stuff.net/
 */
public class SVGSwingBean extends JPanel implements IServoyAwareBean, IComponent, IScriptObject {

	private static final long serialVersionUID = 1L;
	
	private static final String GLOBALS_PREFIX = "globals.";
	
	private static final String SET_VISIBLE = "setVisible";
	private static final String SET_XML = "setDocumentXML";
	private static final String CREATE_MOUSETRACKER = "createMouseTracker";
	
	//private static final String svgNS = "http://www.w3.org/2000/svg";
	
	private IClientPluginAccess app;

	private JSVGCanvas svgCanvas = new JSVGCanvas();
    
    private Document document = null;
    //private Window window = null;
    private Hashtable eventMap = new Hashtable();
    private List mouseList = new ArrayList();
    private boolean documentLoaded = false;
    private Thread documentLoader;
    private String loadFunction;
    private boolean showInStatus;
    private boolean allowUserInteractions;
    
    private String defaultURI;
    private String dataProvider;
    private boolean validationEnabled = true;
    
    private MouseTracker currentMouseTracker;
    private Hashtable mouseMoveTasks = new Hashtable();
    private Hashtable mouseUpTasks = new Hashtable();
    private Hashtable mouseDownTasks = new Hashtable();
    private Color background;
    

    public SVGSwingBean() {
    	setLayout(new BorderLayout());
    	
    	svgCanvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
    	svgCanvas.setEnableRotateInteractor(false);
        svgCanvas.addSVGLoadEventDispatcherListener( new SVGLoadEventDispatcherAdapter() {
                public void svgLoadEventDispatchStarted
                    (SVGLoadEventDispatcherEvent e) {
                    // At this time the document is available...
                    document = svgCanvas.getSVGDocument();
                    if (app != null) {
                    	Locale l = app.getResourceBundle(null).getLocale();
                    	((SVGOMDocument)document).setLocale(l);
                    }
                    // ...and the window object too.
                    // window = svgCanvas.getUpdateManager().getScriptingEnvironment().createWindow();
                    // Registers the listeners on the document
                    // just before the SVGLoad event is dispatched.
                    registerListeners();
                }
            });
    	
        add("Center", svgCanvas);
        addActions();

    }
    
	public void initialize(IClientPluginAccess app) {
		this.app = app;
	}

	public void registerListeners() {
		registerClickEvents();
		registerTrackerEvents();
	}
    
    protected void registerClickEvents() {
    	// Gets an element from the loaded document.
    	if (eventMap.size() > 0) {
    		for (Iterator iterator = eventMap.keySet().iterator(); iterator.hasNext();) {
				String id = (String) iterator.next();
				String elementAction = (String) eventMap.get(id);
		    	try {
			        Element elt = getElementById(id, true);
			        if (elt != null) {
				        EventTarget t = (EventTarget)elt;
				
				        // Adds a 'onclick' listener
				        t.addEventListener(SVGConstants.SVG_EVENT_CLICK, new OnClickAction(elementAction), false);
				        // set the cursor to pointer:
				        svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
			        		new ClickPointer(elt)
			        	);
			        }
		    	} catch (Exception ex) {
		    		setStatus(MessageFormat.format(MessageBundle.getString("svg.error.idNotFound"), new String[] {id}));
		    	}
    		}
    	}
	}
    
    protected void registerTrackerEvents() {
    	if (mouseList.size() > 0) {
    		for (Iterator iterator = mouseList.iterator(); iterator.hasNext();) {
				MouseTracker mt = (MouseTracker) iterator.next();
				String id = mt.getNodeId();
				if (id != null) {
					try {
						Element elt = getElementById(id, true);
						if (elt != null) {
							 EventTarget t = (EventTarget)elt;
							 t.addEventListener(SVGConstants.SVG_EVENT_MOUSEDOWN, new OnMouseDownAction(mt), false);
						}
						String constrainNode = mt.getConstraintNode();
						Element parent = null;
						if (constrainNode != null) {
							parent = (Element) document.getElementById(constrainNode) ;
						}
						if (parent == null) {
							parent = (Element) document.getDocumentElement();
						}
						if (parent != null) {
							 EventTarget t = (EventTarget)parent;
							 t.addEventListener(SVGConstants.SVG_EVENT_MOUSEMOVE, new OnMouseMoveAction(), false);
						}
						parent = (Element) document.getDocumentElement();
						if (parent != null) {
							EventTarget t = (EventTarget)parent;
							t.addEventListener(SVGConstants.SVG_EVENT_MOUSEUP, new OnMouseUpAction(), false);
						}
					} catch (Exception ex) {
						
					}
				}
			}
    	}
    }
    
    private void addActions() {
        // Set the JSVGCanvas listeners.
        svgCanvas.addSVGDocumentLoaderListener(new SVGDocumentLoaderAdapter() {
            public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
            	setStatus(MessageBundle.getString("event.svg.documentLoadingStarted"));
                documentLoaded = false;
                clearMouseMaps();
            }
            public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
            	setStatus(MessageBundle.getString("event.svg.documentLoadingCompleted"));
            }
        });

        svgCanvas.addGVTTreeBuilderListener(new GVTTreeBuilderAdapter() {
            public void gvtBuildStarted(GVTTreeBuilderEvent e) {
            	setStatus(MessageBundle.getString("event.svg.gvtBuildStarted"));
            }
            public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
            	setStatus(MessageBundle.getString("event.svg.gvtBuildCompleted"));
            }
        });

        svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
            public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
            	setStatus(MessageBundle.getString("event.svg.gvtRenderingPrepare"));
            }
            public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
                documentLoaded = true;
                if (documentLoader != null) {
                	if (documentLoader.isAlive()) {
                		try {
                			documentLoader.interrupt();
                		} catch (Exception ex) {}
                	}
                }
                documentLoader = null;
                setStatus("");
                if (app!=null && app.getCurrentWindow() != null) {
                	app.getCurrentWindow().invalidate();
                }
                if (loadFunction != null) {
                	runFunctionByName(loadFunction);
                }
            }
        });

    }

	/* (non-Javadoc)
	 * @see com.servoy.j2db.ui.IComponent#getId()
	 */
	public String getId() {
		return null;
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.ui.IComponent#setComponentEnabled(boolean)
	 */
	public void setComponentEnabled(boolean enabled) {
		setEnabled(enabled);
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.ui.IComponent#setComponentVisible(boolean)
	 */
	public void setComponentVisible(boolean visible) {
		setVisible(visible);
	}

	/**
	 * Generic helper callback function
	 * 
	 * @param functionName
	 * @param app
	 * @param params
	 */
	public void runFunctionByName(String functionName) {
		if (functionName != null) {
			String fName = functionName.trim();
			if (fName.length() > 0) {
				boolean global = fName.toLowerCase().startsWith(GLOBALS_PREFIX);
				if (global) {
					runGlobalFunctionByName(fName, app, null);
				} else {
					runFormFunctionByName(fName, app, null);
				}
			}
		}
	}
	
	/**
	 * Generic callback to a "global" Function
	 * @param fName
	 * @param app
	 * @param params
	 */
	private void runGlobalFunctionByName(String fName, IClientPluginAccess app, Object[] params) {
		try {
			fName = fName.substring(GLOBALS_PREFIX.length());
			app.executeMethod(null, fName, params, false);
		} catch (Exception ex) {
			Debug.error(ex);
		}
	}
	
	
	/**
	 * Generic callback to a "form" Function
	 * 
	 * @param fName
	 * @param app
	 * @param params
	 */
	private void runFormFunctionByName(String fName, IClientPluginAccess app, Object[] params) {
		IFormManager manager = app.getFormManager();
		if (manager != null) {
			IForm form = null;
			int indexOfDot = fName.indexOf('.');
			if (indexOfDot > -1) {
				form = manager.getForm(fName.substring(0, indexOfDot));
				fName = fName.substring(indexOfDot+1);
			} else {
				form = manager.getCurrentForm();
			}
			if (form != null) {
				String formName = form.getName();
				if (formName != null) {
					try {
						app.executeMethod(formName, fName, params, false);
					} catch (Exception ex) {
						Debug.error(ex);
					}
				}
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.scripting.IScriptObject#getAllReturnedTypes()
	 */
	public Class[] getAllReturnedTypes() {
		return new Class[] {MouseTracker.class};
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.scripting.IScriptObject#getParameterNames(java.lang.String)
	 */
	public String[] getParameterNames(String methodName) {
		if (SET_VISIBLE.equals(methodName)) {
			return new String[] { getParams(SET_VISIBLE,1) , 
					getParams(SET_VISIBLE,2) };
		} else if (SET_XML.equals(methodName)) {
			return new String[] { getParams(SET_XML,1) , 
					getParams(SET_XML,2) };
		} else if (CREATE_MOUSETRACKER.equals(methodName)) {
			return new String[] { getParams(CREATE_MOUSETRACKER,1), 
					getParams(CREATE_MOUSETRACKER,2), 
					getParams(CREATE_MOUSETRACKER,3), 
					getParams(CREATE_MOUSETRACKER,4), 
					getParams(CREATE_MOUSETRACKER,5) };
		} else {
			return new String[] { getParams(methodName) };
		}
	}
	
	/* (non-Javadoc)
	 * @see com.servoy.j2db.scripting.IScriptObject#getSample(java.lang.String)
	 */
	public String getSample(String methodName) {
		return getSampleMsg(methodName);
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.scripting.IScriptObject#getToolTip(java.lang.String)
	 */
	public String getToolTip(String methodName) {
		return getTooltipMsg(methodName);
	}

	/* (non-Javadoc)
	 * @see com.servoy.j2db.scripting.IScriptObject#isDeprecated(java.lang.String)
	 */
	public boolean isDeprecated(String methodName) {
		return false;
	}

	public void js_setCallbacks(Scriptable s) {
		if (s != null) {
			Object[] ids = s.getIds();
			if (ids != null && ids.length > 0) {
				eventMap.clear();
				String id = null;
				for (int i = 0; i < ids.length; i++) {
					try {
						id = (String) ids[i];
						String function = (String) s.get(id, s);
						eventMap.put(id, function);
					} catch (Exception ex) {
						setWarning(MessageFormat.format(MessageBundle.getString("svg.error.wrongTypeCallBack"), new String[] {id}));
					}
				}
				setStatus(MessageBundle.getString("event.svg.done"));
			}
		}
	}
	
	public void js_setMouseTrackers(MouseTracker[] trackers) {
		if (trackers != null) {
			mouseList.clear();
			clearMouseMaps();
			for (int i = 0; i < trackers.length; i++) {
				mouseList.add(trackers[i]);
			}
		}
	}
	
	public MouseTracker[] js_getMouseTrackers() {
		if (mouseList.size() > 0) {
			MouseTracker[] trackers = new MouseTracker[mouseList.size()];
			int i = 0;
			for (Iterator iterator = mouseList.iterator(); iterator.hasNext();) {
				MouseTracker mt = (MouseTracker) iterator.next();
				if (mt.getX() == null || mt.getY() == null) {
					setInitialLocation(mt);
				}
				trackers[i++] = mt;
			}
			return trackers;
		}
		return null;
	}

	/**
	 * @param mt
	 */
	private void setInitialLocation(MouseTracker mt) {
		Element elt = (Element)document.getElementById(mt.getNodeId());
		if (elt != null) {
			String s = isCircle(elt) ? SVGConstants.SVG_CX_ATTRIBUTE : SVGConstants.SVG_X_ATTRIBUTE;
			double x = Utils.getAsDouble(elt.getAttribute(s));
			s = isCircle(elt) ? SVGConstants.SVG_CY_ATTRIBUTE : SVGConstants.SVG_Y_ATTRIBUTE;
			double y = Utils.getAsDouble(elt.getAttribute(s));
			mt.setLocation(x, y);
			mt.saveLocation();
		}
	}

	/**
	 * @param elt
	 * @return
	 */
	private boolean isCircle(Element elt) {
		return (elt instanceof SVGCircleElement || elt instanceof SVGEllipseElement);
	}
	
	public void js_setOpaque(boolean b) {
		setOpaque(b);
	}
	
	public boolean js_getOpaque() {
		return isOpaque();
	}
	
	public void js_setBgcolor(String paramString) {
		setBackground(PersistHelper.createColor(paramString));
	}
	public String js_getBgcolor() {
		return (getBackground() == null) ? null : PersistHelper.createColorString(getBackground());
	}
	
	public void js_resetPoints() {
		if (documentLoaded && mouseList.size() > 0) {
			for (Iterator iterator = mouseList.iterator(); iterator.hasNext();) {
				MouseTracker mt = (MouseTracker) iterator.next();
				if (mt.getX() == null || mt.getY() == null) {
					setInitialLocation(mt);
				}
			}
			svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
    			new ResetAllTask()
    		);
		}
	}
	
	public void js_savePoints() {
		if (documentLoaded && mouseList.size() > 0) {
			for (Iterator iterator = mouseList.iterator(); iterator.hasNext();) {
				MouseTracker mt = (MouseTracker) iterator.next();
				mt.saveLocation();
			}
		}
	}
	
	public void js_removeMouseTracker(MouseTracker ms) {
		
		mouseList.remove(ms);
	}
	
	public void js_removeMouseTracker(String id) {
		if (id != null && mouseList.size() > 0) {
			for (int i = 0; i < mouseList.size(); i++) {
				MouseTracker mt = (MouseTracker) mouseList.get(i);
				if (id.equals(mt.getNodeId())) {
					mouseList.remove(i);
					break;
				}
			}
		}
	}
	
	public void js_addMouseTracker(MouseTracker ms) {
		mouseList.add(ms);
	}
	
	public void js_clearMouseTrackers() {
		mouseList.clear();
	}
	
	public MouseTracker js_createMouseTracker(String id, String related) {
		return js_createMouseTracker(id, null, null, related, null, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, String related, Integer index) {
		return js_createMouseTracker(id, null, null, related, index, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, String related, Integer index, String constrain) {
		return js_createMouseTracker(id, null, null, related, index, constrain);
	}
	
	public MouseTracker js_createMouseTracker(String id) {
		return js_createMouseTracker(id, null, null, null, null, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, Double x, Double y) {
		return js_createMouseTracker(id, x, y, null, null, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, Double x, Double y, String related) {
		return js_createMouseTracker(id, x, y, related, null, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, Double x, Double y, String related, Integer index) {
		return js_createMouseTracker(id, x, y, related, index, null);
	}
	
	public MouseTracker js_createMouseTracker(String id, Double x, Double y, String related, Integer index, String constrain) {
		return new MouseTracker(id, x, y, related, index, constrain);
	}
	
	public MouseTracker js_createMouseTracker(Object[] s) {
		return new MouseTracker(s);
	}
	
	public void js_clearCanvas() {
		documentLoaded = false;
		svgCanvas.setSVGDocument(null);
		document = null;
	}
	
	public void js_notifyLoaded(String function) {
		this.loadFunction = function;
	}
		
	public boolean js_isLoaded() {
		return documentLoaded;
	}
	
	public void js_setVisible(String id, boolean visible) {
		if (id != null) {
			js_setVisible(new String[] {id}, visible);
		}
	}
	
	public void js_setVisible(String[] ids, boolean visible) {
		if (documentLoaded && ids != null && ids.length > 0) {
			svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
				new ToggleVisibility(ids, visible)
			);
		}
	}
	
	public String js_setDocumentPath(String path) {
		try {
			SVGDocument doc = new SVGLoader().createSVGDocumentFromPath(path);
			documentLoaded = false;
			clearMouseMaps();
			svgCanvas.setDocument(doc);
		} catch(SVGLoadException ex) {
			return ex.getMessage();
		}
		return null;
	}
	
	public String js_setDocumentXML(String xml) {
		return js_setDocumentXML(xml, null);
	}
	
	public String js_setDocumentXML(String xml, String uri) {
		try {
			if (uri == null) {
				uri = defaultURI;
			}
			SVGDocument doc = new SVGLoader().createSVGDocumentFromXML(xml, uri);
			documentLoaded = false;
			clearMouseMaps();
			svgCanvas.setDocument(doc);
		} catch(SVGLoadException ex) {
			return ex.getMessage();
		}
		return null;
	}
	
	public String js_setDocumentURL(String urlString) {
		try {
			SVGLoader svgloader = new SVGLoader();
			URL url = svgloader.getURL(urlString);
			setStatus(MessageBundle.getString("evt.svg.svgLoader.getURL"));
			InputStream is = svgloader.getInputStream(url);
			setStatus(MessageBundle.getString("evt.svg.svgLoader.getInputStream"));
			documentLoader = new Thread(new URLLoader(url, is));
			documentLoader.start();
			setStatus(MessageBundle.getString("evt.svg.svgLoader.start"));
		} catch (Exception ex) {
			return ex.getMessage();
		}
		return null;
	}
	
	public boolean js_isVisible(String id) {
        Element elt = getElementById(id);
        if (elt != null) {
	        return CSSUtilities.convertDisplay(elt);
        }
		return false;
	}
	
	public void js_setAllowUserInteractions(boolean b) {
		setAllowUserInteractions(b);
	}
	
	public boolean js_isAllowUserInteractions() {
		return isAllowUserInteractions();
	}
		
	public void js_setShowInStatus(boolean b) {
		setShowInStatus(b);
	}
	
	public boolean js_isShowInStatus() {
		return isShowInStatus();
	}
	
	public String js_getDefaultURI() {
		return getDefaultURI();
	}

	public void js_setDefaultURI(String defaultURI) {
		setDefaultURI(defaultURI);
	}
	
	private Element getElementById(String id) {
		return getElementById(id, documentLoaded);
	}
	
	private Element getElementById(String id, boolean loaded) {
		return (loaded && document != null) ? document.getElementById(id) : null;
	}
	
	
	protected class URLLoader implements Runnable {
		private InputStream is;
		private URL url;
		public URLLoader(URL url, InputStream is) {
			this.url = url;
			this.is = is;
		}
		public void run() {
			try {
				SVGDocument doc = new SVGLoader().createSVGDocumentFromInputStream(url, is);
				documentLoaded = false;
				clearMouseMaps();
				svgCanvas.setDocument(doc);
			} catch(SVGLoadException ex) {
				Debug.error(ex.getMessage());
			}
		}
	}
	
	protected class ToggleVisibility implements Runnable {
		private String[] ids;
		boolean visible;
		
		public ToggleVisibility(String[]ids, boolean visible) {
			this.ids = ids;
			this.visible = visible;
		}
		
    	public void run() {
    		for (int i = 0; i < ids.length; i++) {
    			Element elt = getElementById(ids[i]);
    			if (elt != null) {
    				if (visible) {
    					elt.setAttribute(CSSConstants.CSS_DISPLAY_PROPERTY, CSSConstants.CSS_INLINE_VALUE);
    				} else {
    					elt.setAttribute(CSSConstants.CSS_DISPLAY_PROPERTY, CSSConstants.CSS_NONE_VALUE);
    				}
    			}
			}
    	}
    }
		
	protected class OnMouseMoveAction implements EventListener {
		public void handleEvent(Event evt) {
			if (currentMouseTracker != null) {
				MouseEvent me = (MouseEvent) evt;
				String id = currentMouseTracker.getNodeId();
				Element elt = document.getElementById(id);
        		if ( elt != null && !elt.equals(me.getTarget()) ) {
					currentMouseTracker.client.setLocation(me.getClientX(), me.getClientY());
					Point2D.Double p = currentMouseTracker.calculatePoint();
					MouseMoveTask mmt = (MouseMoveTask)mouseMoveTasks.get(id);
					if (mmt == null) {
						mmt = new MouseMoveTask(elt, p);
						mouseMoveTasks.put(id, mmt);
					} else {
						mmt.setP(p);
					}
					svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
						mmt
					);
					if (showInStatus) {
	        			app.setStatusText("X: " + Math.round(p.getX()) + " / Y: " + Math.round(p.getY()));
	        		}
        		}
			}
		}
	}
	
	protected class MouseMoveTask implements Runnable {
		private Element elt;
		private Point2D.Double p;
		public MouseMoveTask(Element elt, Point2D.Double p) {
			this.elt = elt;
			this.p = p;
		}
		public void setP(Point2D.Double p) {
			this.p = p;
		}
		public void run() {
			if (currentMouseTracker != null) {
				if (isCircle(elt)) {
					elt.setAttribute(SVGConstants.SVG_CX_ATTRIBUTE, ""+p.getX());
					elt.setAttribute(SVGConstants.SVG_CY_ATTRIBUTE, ""+p.getY());
				} else {
					elt.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, ""+p.getX());
					elt.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, ""+p.getY());
				}
				if (currentMouseTracker.point != null) {
					SVGPoint point = currentMouseTracker.point;
					point.setX((float)p.getX());
					point.setY((float)p.getY());
				}
			}
		}
	}
	
	protected class OnMouseUpAction implements EventListener {
        public void handleEvent(Event evt) {
        	if (currentMouseTracker != null) {
        		String id = currentMouseTracker.getNodeId();
        		Element elt = document.getElementById(id);
        		if (elt != null) {
                	MouseUpTask mmt = (MouseUpTask)mouseUpTasks.get(id);
					if (mmt == null) {
						mmt = new MouseUpTask(elt, currentMouseTracker.radius);
						mouseUpTasks.put(id, mmt);
					} else {
						mmt.setRadius(currentMouseTracker.radius);
					}
                	svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
            			mmt
            		);
        		}
        		if (showInStatus) {
        			app.setStatusText("");
        		}
        	}
        	currentMouseTracker = null;
        }
	}
	
	protected class MouseUpTask implements Runnable {
		private Element elt;
		private double radius;
		public MouseUpTask(Element elt, double radius) {
			this.elt = elt;
			this.radius = radius;
		}
		public void setRadius(double radius) {
			this.radius = radius;
		}
		public void run() {
			if (isCircle(elt)) {
				elt.setAttribute(SVGConstants.SVG_R_ATTRIBUTE, ""+radius);
			}
			elt.removeAttribute(CSSConstants.CSS_POINTER_EVENTS_PROPERTY);
			document.getDocumentElement().removeAttribute(CSSConstants.CSS_CURSOR_PROPERTY);
		}
	}
	
	protected class ResetAllTask implements Runnable {
		public void run() {
			for (Iterator iterator = mouseList.iterator(); iterator.hasNext();) {
				MouseTracker mt = (MouseTracker) iterator.next();
				if (mt.getNodeId() != null && mt.getInitialX() != null && mt.getInitialY() != null) {
					Element elt = document.getElementById(mt.getNodeId());
					if (elt != null) {
						mt.resetLocation();
						if (isCircle(elt)) {
	    					elt.setAttribute(SVGConstants.SVG_CX_ATTRIBUTE, ""+mt.getX());
	    					elt.setAttribute(SVGConstants.SVG_CY_ATTRIBUTE, ""+mt.getY());
	    				} else {
	    					elt.setAttribute(SVGConstants.SVG_X_ATTRIBUTE, ""+mt.getX());
	    					elt.setAttribute(SVGConstants.SVG_Y_ATTRIBUTE, ""+mt.getY());
	    				}
						if (mt.getRelated() != null) {
							elt = document.getElementById(mt.getRelated());
		            		if (elt != null && elt instanceof SVGAnimatedPoints) {
		            			int index = 0;
		            			if (mt.getRelatedIndex() != null) {
		            				index = Utils.getAsInteger(mt.getRelatedIndex());
		            			}
		            			SVGPointList liste = ((SVGAnimatedPoints)elt).getPoints();
		            			if (liste != null && index < liste.getNumberOfItems()) {
		            				SVGPoint point = liste.getItem(index);
		            				mt.point = point;
		            				point.setX(mt.getX().floatValue());
		            				point.setY(mt.getY().floatValue());
		            			}
		            		}
		            	}
					}
				}
			}
		}
	}
	
	protected class OnMouseDownAction implements EventListener {
		private MouseTracker tracker;
		public OnMouseDownAction(MouseTracker tracker) {
			this.tracker = tracker;
		}
		
        public void handleEvent(Event evt) {
        	MouseEvent me = (MouseEvent) evt;
            Element elt = (Element)me.getTarget();
            
            if (isCircle(elt)) {
            	 double radius = Utils.getAsDouble(elt.getAttribute(SVGConstants.SVG_R_ATTRIBUTE));
            	 double x = Utils.getAsDouble(elt.getAttribute(SVGConstants.SVG_CX_ATTRIBUTE));
            	 double y = Utils.getAsDouble(elt.getAttribute(SVGConstants.SVG_CY_ATTRIBUTE));
            	 tracker.radius = radius;
            	 tracker.currentX = x;
            	 tracker.currentY = y;
            	 
            } else {
            	 double x = Utils.getAsDouble(elt.getAttribute(SVGConstants.SVG_X_ATTRIBUTE));
            	 double y = Utils.getAsDouble(elt.getAttribute(SVGConstants.SVG_Y_ATTRIBUTE));
	           	 tracker.currentX = x;
	           	 tracker.currentY = y;
            }
            
            if (tracker.getRelated() != null) {
            	Element related = document.getElementById(tracker.getRelated());
            	if (related != null) {
            		if (related instanceof SVGAnimatedPoints) {
            			int index = 0;
            			if (tracker.getRelatedIndex() != null) {
            				index = Utils.getAsInteger(tracker.getRelatedIndex());
            			}
            			SVGPointList liste = ((SVGAnimatedPoints)related).getPoints();
            			if (liste != null && index < liste.getNumberOfItems()) {
            				SVGPoint point = liste.getItem(index);
            				tracker.point = point;
            			}
            		}
            	}
            }
            AffineTransform all = (AffineTransform)svgCanvas.getViewingTransform().clone();
            all.concatenate(svgCanvas.getRenderingTransform());
            tracker.scale = all.getScaleX();
            double transX = all.getTranslateX();
            double transY = all.getTranslateY();
            tracker.trans.setLocation(transX, transY);
            
            tracker.client.setLocation(me.getClientX(),me.getClientY());
            tracker.calculateDistance();
        	currentMouseTracker = tracker;
        	String id = tracker.getNodeId();
        	MouseDownTask mdt = (MouseDownTask) mouseDownTasks.get(id);
        	if (mdt == null) {
        		mdt = new MouseDownTask(elt);
        		mouseDownTasks.put(id, mdt);
        	}
        	svgCanvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(
        		mdt
        	);
        }
	}
	
	protected class MouseDownTask implements Runnable {
		private Element elt;
		public MouseDownTask(Element elt) {
			this.elt = elt;
		}
		public void run() {
			if (currentMouseTracker != null) {
				if (isCircle(elt)) {
					elt.setAttribute(SVGConstants.SVG_R_ATTRIBUTE, ""+currentMouseTracker.radius*1.5);
				}
				elt.setAttribute(CSSConstants.CSS_POINTER_EVENTS_PROPERTY, CSSConstants.CSS_NONE_VALUE);
				document.getDocumentElement().setAttribute(CSSConstants.CSS_CURSOR_PROPERTY, CSSConstants.CSS_CROSSHAIR_VALUE);
			}
		}
	}
		
	protected class OnClickAction implements EventListener {
    	private String elementAction;
    	
    	public OnClickAction(String elementAction) {
    		this.elementAction = elementAction;
    	}
        public void handleEvent(Event evt) {
        	runFunctionByName(elementAction);
        }
    }

	protected class ClickPointer implements Runnable {
    	private Element elt;
    	
    	public ClickPointer(Element elt) {
    		this.elt = elt;
    	}
        public void run() {
        	elt.setAttribute(CSSConstants.CSS_CURSOR_PROPERTY, CSSConstants.CSS_POINTER_VALUE);
        }
    }
	

	public boolean isShowInStatus() {
		return showInStatus;
	}

	public void setShowInStatus(boolean showInStatus) {
		this.showInStatus = showInStatus;
	}

	public boolean isAllowUserInteractions() {
		return allowUserInteractions;
	}

	public void setAllowUserInteractions(boolean allowUserInteractions) {
		this.allowUserInteractions = allowUserInteractions;
		svgCanvas.setDisableInteractions(!allowUserInteractions);
		svgCanvas.setEnableRotateInteractor(false);
	}

	public void setSelectedRecord(IRecord record) {
		if (hasDataProvider() && isContainedInFoundset(record, dataProvider)) { 
			String xml = (String) record.getValue(dataProvider);
			if (xml != null) {
				documentLoaded = false;
				js_setDocumentXML(xml, null);
			} else {
				if (validationEnabled) {
					js_clearCanvas();
				}
			}
		}
	}

	public boolean isReadOnly() {
		return true;
	}

	public void setValidationEnabled(boolean validationEnabled) {
		this.validationEnabled = validationEnabled;
	}

	public boolean stopUIEditing(boolean paramBoolean) {
		return true;
	}

	public String getDataProvider() {
		return dataProvider;
	}

	public void setDataProvider(String dataProvider) {
		this.dataProvider = dataProvider;
	}
	
	public boolean hasDataProvider() {
		return (dataProvider != null && dataProvider.trim().length() > 0);
	}

	/**
	 * @param record
	 * @param dataProvider
	 * @return true id the dataProvider exists in the FoundSet where the record is originating
	 */
	public static boolean isContainedInFoundset(IRecord record, String dataProvider) {
		boolean isContained = (record != null);
		if (isContained) {
			IFoundSet fs = record.getParentFoundSet();
			isContained = fs != null;
			if (isContained) {
				String[] providers = fs.getDataProviderNames(IFoundSet.COLUMNS);
				if (providers != null) {
					for (int i = 0; i < providers.length; i++) {
						if (dataProvider.equals(providers[i])) {
							isContained = true;
							break;
						}
					}
				}
				if (isContained && fs.getSelectedIndex() > -1) {
					return true;
				}
				if (fs.containsDataProvider(dataProvider)) {
					return true;
				}
				isContained = false;
			}
		}
		return isContained;
	}

	/**
	 * @return the defaultURI
	 */
	public String getDefaultURI() {
		return this.defaultURI;
	}

	/**
	 * @param defaultURI the defaultURI to set
	 */
	public void setDefaultURI(String defaultURI) {
		this.defaultURI = defaultURI;
	}
	
	private void setStatus(String s) {
		if (showInStatus && app != null) {
			app.setStatusText(s);
		}
	}
	
	private void setWarning(String s) {
		if (showInStatus && app != null) {
			app.setStatusText(s);
		} else {
			Debug.error(s);
		}
	}

	/**
	 * 
	 */
	private void clearMouseMaps() {
		currentMouseTracker = null;
		mouseMoveTasks.clear();
		mouseUpTasks.clear();
		mouseDownTasks.clear();
	}

	private String getParams(String s) {
		return getParams(s, 0);
	}
	
	private String getParams(String s, int x) {
		return getMessage("svg.params.", s, x);
	}
	
	private String getTooltipMsg(String s) {
		return getMessage("svg.tooltip.", s, 0);
	}
	
	private String getSampleMsg(String s) {
		return MessageFormat.format(getMessage("svg.sample.", s, 0), new String[] {s});
	}
	
	private String getMessage(String prefix, String s, int x) {
		String postfix = (x > 0) ? "."+x : "";
		return MessageBundle.getString(prefix+s+postfix);
	}


	/**
	 * @param opaque the opaque to set
	 */
	public void setOpaque(boolean b) {
		super.setOpaque(b);
		if (getBackground() == null) {
			Color color = new Color(0,0,0,0);
			setBackground(color);
		} else {
			setBackground(getBackground());
		}
	}

	/**
	 * @return the background
	 */
	public Color getBackground() {
		return background;
	}

	/**
	 * @param background the background to set
	 */
	public void setBackground(Color color) {
		if (color != null) {
			int alpha = isOpaque() ? 255 : 0;
			this.background = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
			super.setBackground(background);
			if (svgCanvas != null) {
				svgCanvas.setBackground(background);
				svgCanvas.setOpaque(isOpaque());	
			}
			revalidate();
		}
	}

}
